package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.cache.GuiGuCache;
import com.atguigu.tingshu.common.constant.KafkaConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.service.KafkaService;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.model.album.TrackInfo;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;

    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;

    @Autowired
    private AlbumStatMapper albumStatMapper;

    @Autowired
    private TrackInfoMapper trackInfoMapper;

    @Autowired
    private KafkaService kafkaService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    /**
     * 新增专辑
     * 1.向专辑信息表新增一条记录
     * 2.向专辑属性关系表新增若干条记录
     * 3.向专辑统计表中新增四条记录
     *
     * @param albumInfoVo 专辑相关信息
     * @param userId      用户ID
     */
    @Override
    @Transactional(rollbackFor = Exception.class)  //Spring事务管理默认捕获RuntimeException才会进行事务回滚
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo, Long userId) {
        //1.向专辑信息表新增一条记录
        //1.1 将前端提交专辑VO对象转为专辑PO对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        //1.2 部分属性赋值
        albumInfo.setUserId(userId);
        //付费类型为非免费的专辑 免费试听集数为5，试听秒数(不限制)
        if (!SystemConstant.ALBUM_PAY_TYPE_FREE.equals(albumInfo.getPayType())) {
            albumInfo.setTracksForFree(5);
        }
        //目前没有平台审核端暂时写为通过
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        //1.3 保存专辑 得到专辑ID
        albumInfoMapper.insert(albumInfo);
        Long albumId = albumInfo.getId();

        //2.向专辑属性关系表新增若干条记录
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            //1.遍历VO集合 将VO转为PO
            albumAttributeValueVoList.forEach(albumAttributeValueVo -> {
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                //2.关联专辑ID
                albumAttributeValue.setAlbumId(albumId);
                //3.新增专辑属性
                albumAttributeValueMapper.insert(albumAttributeValue);  //一个记录执行一条insert MP业务层提供批量新增方法-效率高
            });
        }
        //3.向专辑统计表中新增四条记录(播放数，订阅数，购买数，评论数)
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_BUY);
        this.saveAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT);

        //4.发送MQ消息通知搜索服务进行上架专辑  审核通过后才上架专辑
        if ("1".equals(albumInfoVo.getIsOpen())) {
            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_UPPER, albumId.toString());
        }
    }

    /**
     * 初始化保存专辑统计信息
     *
     * @param albumId  专辑ID
     * @param statType 统计类型
     */
    @Override
    public void saveAlbumStat(Long albumId, String statType) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        albumStat.setStatNum(0);
        albumStatMapper.insert(albumStat);
    }


    /**
     * 分页查询当前用户专辑列表
     *
     * @param pageInfo       MP的分页对象
     * @param albumInfoQuery 查询条件
     * @return
     */
    @Override
    //@Cacheable()  //只有缓存功能，没有分布式锁功能 默认情况：缓存数据在JVM内存中
    public Page<AlbumListVo> getUserAlbumByPage(Page<AlbumListVo> pageInfo, AlbumInfoQuery albumInfoQuery) {
        return albumInfoMapper.getUserAlbumByPage(pageInfo, albumInfoQuery);
    }

    /**
     * 根据专辑ID删除专辑
     * 1.根据主键ID删除专辑
     * 2.根据专辑ID删除统计列表
     * 3.根据专辑ID删除专辑属性列表
     * 4.根据专辑ID删除声音列表
     *
     * @param id
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeAlbumInfo(Long id) {
        //1.根据主键ID删除专辑
        albumInfoMapper.deleteById(id);

        //2.根据专辑ID删除统计列表
        LambdaQueryWrapper<AlbumStat> albumStatLambdaQueryWrapper = new LambdaQueryWrapper<>();
        albumStatLambdaQueryWrapper.eq(AlbumStat::getAlbumId, id);
        albumStatMapper.delete(albumStatLambdaQueryWrapper);

        //3.根据专辑ID删除专辑属性列表
        LambdaQueryWrapper<AlbumAttributeValue> albumAttributeValueLambdaQueryWrapper = new LambdaQueryWrapper<>();
        albumAttributeValueLambdaQueryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        albumAttributeValueMapper.delete(albumAttributeValueLambdaQueryWrapper);

        //4.根据专辑ID删除声音列表
        LambdaQueryWrapper<TrackInfo> trackInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        trackInfoLambdaQueryWrapper.eq(TrackInfo::getAlbumId, id);
        trackInfoMapper.delete(trackInfoLambdaQueryWrapper);


        kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_LOWER, id.toString());
    }

    /**
     * 先采用SpringDataRedis分布式锁 解决缓存击穿
     * 根据专辑ID查询专辑信息包含专辑属性列表
     *
     * @param id
     * @return
     */
    //@Override
    //public AlbumInfo getAlbumInfo(Long id) {
    //    try {
    //        //1.优先从缓存中获取业务数据
    //        //1.1 构建业务数据Key
    //        String dataKey = RedisConstant.ALBUM_INFO_PREFIX + id;
    //        //1.2 查询Redis中缓存数据
    //        AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
    //        //1.3 如果命中缓存则直接返回即可，未命中缓存，执行第二步
    //        if (albumInfo != null) {
    //            return albumInfo;
    //        }
    //        //2.尝试获取分布式锁
    //        //2.1 构建锁的Key
    //        String lockKey = RedisConstant.ALBUM_LOCK_PREFIX + id + RedisConstant.CACHE_LOCK_SUFFIX;
    //        //2.2 产生锁的值：锁标识UUID
    //        String lockValue = IdUtil.fastSimpleUUID();
    //
    //        //2.3 尝试获取锁，利用set ex nx命令实现分布式锁
    //        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, RedisConstant.ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
    //
    //        //2.4 获取锁成功执行业务
    //        if (flag) {
    //            try {
    //                //3.执行业务（查库业务，将查询结果放入缓存）
    //                albumInfo = this.getAlbumInfoFromDB(id);
    //                //3.1 查询数据库如果为空 将空数据有加入缓存中，设置短暂时间:10分钟
    //                if (albumInfo == null) {
    //                    redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
    //                    return albumInfo;
    //                }
    //                //3.2 查询数据库如果有值 将空数据有加入缓存中，设置时间：基础时间(1小时)+随机值
    //                int ttl = RandomUtil.randomInt(500, 3600);
    //                redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TIMEOUT + ttl, TimeUnit.SECONDS);
    //                return albumInfo;
    //            } finally {
    //                //4.释放锁 采用lua脚本释放锁
    //                String text = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                        "then\n" +
    //                        "    return redis.call(\"del\",KEYS[1])\n" +
    //                        "else\n" +
    //                        "    return 0\n" +
    //                        "end";
    //                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    //                redisScript.setScriptText(text);
    //                redisScript.setResultType(Long.class);
    //                redisTemplate.execute(redisScript, Arrays.asList(lockKey), lockValue);
    //            }
    //        } else {
    //            //2.5 获取锁失败-自旋
    //            Thread.sleep(200);
    //            return this.getAlbumInfo(id);
    //        }
    //    } catch (Exception e) {
    //        log.error("【专辑服务】获取专辑异常：{}", e);
    //        //兜底处理，查询数据库返回业务数据
    //        return this.getAlbumInfoFromDB(id);
    //    }
    //}


    /**
     * 先采用Redisson分布式锁 解决缓存击穿
     * 根据专辑ID查询专辑信息包含专辑属性列表
     *
     * @param id
     * @return
     */
    @Override
    public AlbumInfo getAlbumInfo(Long id) {
        try {
            //1.优先从缓存中获取业务数据
            //1.1 构建缓存业务数据Key 形式：前缀：+ 主键标识
            String dataKey = RedisConstant.ALBUM_INFO_PREFIX + id;
            //1.2 查询Redis缓存
            AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
            //1.3 命中缓存返回即可
            //1.4 未命中缓存则执行获取分布式锁，避免缓存击穿
            if (albumInfo != null) {
                return albumInfo;
            }
            //2.获取分布式锁
            //2.1 构建锁Key
            String lockKey = RedisConstant.ALBUM_LOCK_PREFIX + id + RedisConstant.CACHE_LOCK_SUFFIX;
            //2.2 创建锁对象
            RLock lock = redissonClient.getLock(lockKey);
            //2.3 获取锁
            lock.lock();

            //3.执行业务
            try {
                //3.1 再次查询一次缓存
                albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(dataKey);
                if (albumInfo != null) {
                    return albumInfo;
                }
                //3.2 缓存未命中，在执行查询数据库
                albumInfo = this.getAlbumInfoFromDB(id);
                //3.3 查询数据库如果为空 将空数据有加入缓存中，设置短暂时间: 10 分钟
                if (albumInfo == null) {
                    redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TEMPORARY_TIMEOUT, TimeUnit.SECONDS);
                    return albumInfo;
                }
                //3.4 查询数据库如果有值 将空数据有加入缓存中，设置时间：基础时间(1小时)+随机值
                int ttl = RandomUtil.randomInt(500, 3600);
                redisTemplate.opsForValue().set(dataKey, albumInfo, RedisConstant.ALBUM_TIMEOUT + ttl, TimeUnit.SECONDS);
                return albumInfo;
            } finally {
                //4.释放锁
                lock.unlock();
            }
        } catch (Exception e) {
            //兜底处理方案：查询数据库
            log.error("[专辑服务]Redis服务不可用，查询DB：{}", e);
            return this.getAlbumInfoFromDB(id);
        }
    }


    /**
     * 根据专辑ID查询专辑信息包含专辑属性列表
     *
     * @param id
     * @return
     */
    @Override
    @GuiGuCache(prefix = "albumInfo:")
    public AlbumInfo getAlbumInfoFromDB(Long id) {
        //1.根据主键查询专辑信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(id);

        //2.根据专辑ID查询专辑属性列表
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(queryWrapper);
        albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);
        return albumInfo;
    }

    /**
     * 专辑修改
     * 1.修改专辑信息
     * 2.修改专辑属性值（先删除旧的属性，再新增）
     *
     * @param id          专辑ID
     * @param albumInfoVo 修改后专辑信息
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateAlbumInfo(Long id, AlbumInfoVo albumInfoVo) {
        //1.修改专辑信息
        //1.1 将专辑VO转为PO对象
        AlbumInfo albumInfo = BeanUtil.copyProperties(albumInfoVo, AlbumInfo.class);
        albumInfo.setId(id);
        //1.2 执行修改专辑信息
        albumInfoMapper.updateById(albumInfo);

        //2.修改专辑属性信息
        //2.1 先根据专辑ID条件删除专辑属性关系(逻辑删除)
        LambdaQueryWrapper<AlbumAttributeValue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AlbumAttributeValue::getAlbumId, id);
        albumAttributeValueMapper.delete(queryWrapper);
        //2.2 再根据用户提交专辑属性新增（关联专辑ID）
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
            albumAttributeValueVoList.forEach(albumAttributeValueVo -> {
                //转为PO对象 关联专辑ID
                AlbumAttributeValue albumAttributeValue = BeanUtil.copyProperties(albumAttributeValueVo, AlbumAttributeValue.class);
                albumAttributeValue.setAlbumId(id);
                albumAttributeValueMapper.insert(albumAttributeValue);
            });
        }
        //修改同时判断公开状态
        if ("1".equals(albumInfoVo.getIsOpen())) {
            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_UPPER, id.toString());
        }
        if ("0".equals(albumInfoVo.getIsOpen())) {
            kafkaService.sendMessage(KafkaConstant.QUEUE_ALBUM_LOWER, id.toString());
        }
    }

    /**
     * 查询当前登录用户所有专辑列表
     *
     * @param userId
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.NEVER)
    public List<AlbumInfo> getUserAllAlbumList(Long userId) {
        //1.构建查询条件
        LambdaQueryWrapper<AlbumInfo> queryWrapper = new LambdaQueryWrapper<>();
        //1.1 根据用户ID查询
        queryWrapper.eq(AlbumInfo::getUserId, userId)
                .select(AlbumInfo::getId, AlbumInfo::getAlbumTitle) //1.2 TODO 指定专辑ID， 专辑标题 列查询
                .orderByDesc(AlbumInfo::getCreateTime) //1.3 根据创建时间倒序  sql limit 0,200
                .last("limit 200"); //1.4 查询200条记录
        //2.执行查询
        return albumInfoMapper.selectList(queryWrapper);
    }

    /**
     * 根据专辑ID获取专辑统计信息
     *
     * @param albumId
     * @return
     */
    @Override
    @GuiGuCache(prefix = "albumStatVo:")
    public AlbumStatVo getAlbumStatVo(Long albumId) {
        return albumInfoMapper.getAlbumStatVo(albumId);
    }
}
